转:Notes on Programming in C

Rob Pike
February 21, 1989
原文地址:http://www.lysator.liu.se/c/pikestyle.html

序言

Kernighan和Plauger写过一本很重要,也很有影响力的一本书,叫做《The Elements of Programming Style》。不过有时我感觉书中总结的规则更像是一种严格的约束编程风格的方法,而不是它们内含哲学的精简恰当的表达。如果像书中所说的那样,变量名应该更有实际的意义,那么一个冗长的有意义的名字真的更好一些么?MaximumValueUntilOverflow比maxval更好么?我可不这么认为。

下面的所有文字都是一些短小的文章,想要把编程中关键的地方说明清楚,而不是给出严格的规则。我不期望你同意我所有的观点,因为观点随着时间都是会改变的,但它们已经在我大脑里聚集了很长时间了,是我大量经验的总结,在此记下来希望能帮助你理解如何去计划一段程序的细节之处(以前我看过一篇教你如何计划所有事情的很好的文章,但它们的内容只是我将要谈到的一部分)。如果你觉得这些建议只是个人习惯,你也不同意它们,这样很好,但是如果你能想想不同意的原因,那就更好了。最后,不要因为我这么说,你就这么做,只按你觉得能达到目标的最好的方法去做,不要在乎我的感受。

欢迎你的评论。

版式问题

程序是一种公开的东西,这意味着它将被一个接一个不同的程序员所阅读(也可能是几天、几星期或者几年后的你自己),最后交给机器处理。机器当然是不关心你的程序写的多么优美-只要程序能够编译通过,机器就很满足了-但作为人就不一样了。有时他们在乎很多:良好的版式布局能让程序的细节更容易理解,就像把英语文章中的介词都用粗体显示一样。尽管一些人认为程序应该看起来就和Algol68报告一样(一些系统甚至要求程序员使用这种风格),但是一个清晰的程序不会因为使用了这种风格而变得更加清晰,同样,一段糟糕的程序只能依旧糟糕。

良好的版式约定能够让程序更清晰,缩进也许是最众所周知也最有用的例子了。所以如果你坚持使用陈旧的无格式的风格,也请你注意别使用愚蠢的版式。千万别刻意修饰程序,例如要保持注释简洁明了。在程序中只表达必要的东西,保持简单性和一致性。

变量的名字

啊,变量的名字呀,长度不是你的美德,清楚的表达才是你应有的作用。全局变量很少使用一个很长的名字,像maxphysaddr这样就挺好。在循环中使用数组的索引没有比i更好的了,使用index或者elementnumber这样的名字更像是在说一种类型,同时也隐藏了它计数的本质。当变量名很长时,很难注意到程序的运行流程。这是版式问题(typographic issue)的一部分,可以考虑

1
2
for(i=0 to 100)
array[i]=0

1
2
for(elementnumber=0 to 100)
array[elementnumber]=0;

实际程序中的这种问题使得程序很糟糕,所以索引并不需要一个有意义的名字,简单就好。

指针也应该有一个清楚的标识。np要比nodepointer更容易记忆,如果你了解一些命名约定,就会知道np就是从node pointer简化来的,下面还会有一些关于命名约定的内容。
一致性很重要,这也是所有易读程序的共同特征,在程序中的命名也应当统一。如果你已经给一个变量命名为maxphysaddr,就不要给它的同类变量命名为lowestaddress。

最后我想说,我更欣赏那些有着最短的长度但却包含最大量信息的名字,让变量的更多意义通过和它相关的程序来表达。比如,全局量通常在程序中使用次数很少,所以它应当有一个更容易让人明白它意义的名字。因此我使用maxphysaddr而不是MaximumPhysicalAddress给全局量命名,给局部指针命名使用np而不是NodePointer。在很大程度这是一种个人品味和习惯,但这方法能让程序更清晰易读。

我避免在变量的名字中使用大写字母,对我的眼睛来说,它们看起来又笨又不舒服,和一个糟糕的排版差不了多少。

指针的使用

C语言的独特之处在于它有指针机制,指针可以指向任何东西。但它是把双刃剑,用的好了,可以让你更有效率,否则带来的破坏将是巨大的。因此很多专业人士认为指针很很危险,但我却认为指针是一个很强大的表达工具,能帮助我们将事情说的更清楚。
考虑:你有一个指针指向一个对象,这听起来很不值一提,但请看下面的两种表达

1
2
np
node[i]

第一个指针直接指向一个节点,第二个通过赋值间接指向同一个地方,可以看见后一种表达并不是很简单,为了理解它,我们必须知道那个节点是什么, i 是什么,还要清楚 i 和 node 在程序中的关系,这样才能搞清这段程序的意思。孤立地使用后一种表达甚至不能告诉我们 i 此时是否合法,有没有越界,更没法判断它是不是我们想要的元素的索引呐。如果 i 、 j 、 k 都是一个数组的索引,那将很容易出错,有时编译器都无能为力,尤其是在给函数赋值的时候:一个指针只指向一个地方;所以一个数组的名字(首地址)和一个元素的索引应该一起作为参数传给函数。
用过于复杂的表达方法引用一个对象很容易犯错,直接使用对象的地址有时候来的更简单,正确的使用指针能简化代码:

1
parent->link[i].type

1
lp->type

如果我们想使用下一个元素,可以这样

1
parent->link[++i].type

或者

1
(++lp)->type.

i增加但其余的程序不变,用指针的话就不用考虑复杂的i呐。
书写格式上应该考虑的事情这里也有。在使用结构的时候,使用指针在方便不过呐:代码更短,而且编译器也更容易处理。一个要注意的就是指针的类型问题,指针不能指向其他类型的数据,否则会出现恼人的错误,这种错误用索引的方式就不会发生。对于一个结构体,它的成员的名字应该反映出它的类型,所以

1
np->left

就足够了;如果一个数组有一个好的名字,那么最后语句就会变的长一些:

1
node[i].left.

又一次,那些额外的字符和最后变大的程序让人恼怒。
作为一条规则,如果你发现你的代码在描述一个数据结构的相关元素时有一些相似的、复杂的表达的话,恰当的使用指针能让事情变得简单。比如:

1
2
3
4
if(goleft)
p->left=p->right->left;
else
p->right=p->left->right;

重复使用 p ,有时为了简化计算,使用临时变量( p )或者一个宏是值得的。

函数名

函数名应该能反映出它的功能和它的返回值。函数经常用在if语句的判断中,这时程序应该像一句话一样能读通。

1
if(checksize(x))

就很不好,我们不能推断出当大小(size)合适时,函数是返回true还是false;而

1
if(validsize(x))

就表达的很清楚,在将来使用函数时也更加不容易犯错。

注释

一个微妙的问题,需要一些判断力。我一直提倡消除不必要的注释,有几个原因:

  1. 用好的变量名来解释代码;
  2. 注释会被编译器忽略,所以正确性无法保证,特别是当代码修改过以后,错误的注释会误导人;
  3. 版式问题:注释让代码混乱。

但是,我有时也注释。仅仅是在我想介绍下面的代码是干什么的时候。比如:全局变量的类型和含义(每一个大型程序中我都注释)、介绍一个很难理解的函数,或者划分不同的程序块。
这是一个非常典型的糟糕的注释方式:

1
i=i+1;           /* Add one to i */

这样更糟糕:

1
2
3
4
5
6
/**********************************
* *
* Add one to i *
* *
**********************************/
i=i+1;

先别笑,直到你在实际的程序中看见。

避免在注释中使用五花八门的样式,避免使用大块的注释,除非接下来的代码是很重要的数据结构声明(对数据注释比对算法注释更有益处);记住:避免注释!如果你的代码需要用注释来帮助理解,那么你最好重写它。